/* 
Copyright (c) 2004,2005, 2006, 2007, 2008 University of Illinois 

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/** @fileoverview
 * <p>Retrieves RDF definitions and implements an accessor interface for RDFTriplesInterface.
 * Uses mozilla functionality to retrieve rdf definitions from a list of URL's, as well as, 
 * recursively, the definitions of the definitions.  Provides an interface that allows one to 
 * iterate through the triples as a flat list.
 * </p>
 * <p>For example, to inspect a "role:checkbox" widget, one would tend to resolve the 
 * pseudo-namespace "role" and then call functionality from this file with the 
 * namespace (where, hopefully, sits a valid rdf), the subject ("checkbox"), and a callback function.  
 * This functionality would download the relavent URL, parse it into RDF triples, attempt to find relationships 
 * involving the subject and it's relationship to other things.  First, the relationships of 
 * class i.e. subClassOf, subPropertyOf will be explored.  These further files found will be treated in a similar way.  
 * When all relationships have been followed, determined to lead nowhere, or have timed out, the 
 * callback function is called with a success code.  Up until now, the callback function has 
 * communicated when and how many new initial extra file requests were being sent out and which 
 * had started, failed, or timed out.  The array of triples is then ready to be used by application logic.
 * </p>
 * @requires RDFTriplesInterface
 */
 
/**
 * Initializes the parser/request object.
 * @constructor
 * @param followPredicate the string by which other resources will be matched and downloaded (i.e. subClassOf)
 * @param callback object with a method parseCallback(isFinished, observerMsg) that will be called with status updates
 */
AccessibilityExtension.ns(function() { with (AccessibilityExtension.LIB) {
	var AE = AccessibilityExtension.ns;  

	function RDFParse(followPredicate, callback) {
		this.userCallback = callback;
		this.followPredicate = followPredicate;
		this.urlsFollowed = new Array();	
	
		// Init datasource resources	
		// ... copied mostly from http://www.xulplanet.com/tutorials/mozsdk/rdfsrcdetails.php
		this.rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);
		this.compositeDS = this.rdfService.GetDataSource("rdf:composite-datasource");
		this.dataSource = new Array();
	}
	
	/* var RDF_LOAD_TIMEOUT_MS = 60000; */
	
	RDFParse.prototype = {
		triplesInterface : null,
		userCallback : function( isFinished, observerMsg ) {},
		urlsFollowed : null, 
		followPredicate : null,
		followSubject : null,
		RDFInterface : null,
		rdfService : null,
		compositeDS : null,
		dataSource : null,	
	/*	loadingTimeoutTimer : null, */
	
		// For accessing data from compositeDS
		dataSourceIter : null, // iterates over the datasources in the composite datasource
		resourceIter : null, // resource iterator
		arcIter : null,	
		targetIter : null,	
		
		nextDataSource : null,
		nextResource : null, // holds value of iterator.getNext()
		nextArc : null,
		nextTarget : null,
	
		/** Removes datasources from the compositeDS.  
		 * Needed to prevent RDF persistance across different pages.
		 * @returns None
		 */
		cleanUp : function() {
			// Iterate through the datasources and remove each.
			var dsaIter = this.compositeDS.GetDataSources();
			while( dsaIter.hasMoreElements() ) { // loop over datasources
				this.compositeDS.RemoveDataSource(dsaIter.getNext());
			}
		},
	
		/** Begins the process of recursively compiling the list of triples given an rdf starting point.
		 * @param subject the subject that should be followed along the already-provided predicate relationship
		 * (i.e. find all files that are &lt;predicate&gt; of "subject" resource).
		 * @param urls to search (often the namespace of an element)
		 * @returns None
		 */
		startParse : function(subject, urls, calledBy) {
		return;
			// Set the particular subject that will be matched for following (i.e. checkbox, to get all the ancestors of checkbox)
			this.followSubject = subject;
	
			// Convert to "cached" URL's
			for(var j in urls) {
				urls[j] = rdfGetCachedURL(urls[j]);
				if(typeof this.urlsFollowed[urls[j].toString()] == "undefined") {
					this.urlsFollowed[urls[j].toString()] = false; // mark that we've started to follow this
				} else {
					urls.splice(j,1); // delete the duplicate 
				}
			}
			
			for(var i in urls) {
				// Get the "seed" documents
				this.startParseURL(urls[i], calledBy);
			}
		},
		/** [private] Like startParse but used in the recursive search behavior. 
		 * These are sub-RDF's that are being followed because they were found in previous RDF's
		 * @param urls array of URL's to follow
		 * @see #startParse
		 */
		continueParse : function(urls) {
			for(var i in urls) {
				// Get the "seed" documents
				this.startParseURL(urls[i], calledBy);
			}
		},
		/** [private] parses the document at url as an RDF file and integrates contents to this datasource.
		 * communicates with the internal callback function
		 * @param url the URL to load and parse
		 * @returns None
		 */
		startParseURL : function(url, calledBy) {
		alert('XXX startParseURL before callback')
			// FIXME: localize
			//this.userCallback.parseCallback(false,"Starting to parse "+url);
			calledBy.parseCallback(false,"Starting to parse "+url);
		alert('XXX startParseURL after callback')
			
			// ... copied somewhat from http://www.xulplanet.com/tutorials/mozsdk/rdfsrcdetails.php				
			var nowThis = this;
			var observer = {
				onBeginLoad : function(sink) { 
	// Note: Firefox doesn't have the sink.URI property				
	//				nowThis.parseURLCallback('onBeginLoad', sink.URI);
					nowThis.parseURLCallback('onBeginLoad', url);
				},
				onInterrupt : function(sink) { 
	//				nowThis.parseURLCallback('onInterrupt', sink.URI); 
					nowThis.parseURLCallback('onInterrupt', url); 
				},
				onResume : function(sink) { 
	//				nowThis.parseURLCallback('onResume', sink.URI); 
					nowThis.parseURLCallback('onResume', url); 
				},
				onError : function(sink, status, msg) { 
	//				nowThis.parseURLCallback('onError', sink.URI); 
					nowThis.parseURLCallback('onError', url); 
				},
				onEndLoad : function(sink){
					sink.removeXMLSinkObserver(this);
	//				nowThis.parseURLCallback('onEndLoad', sink.URI); 
					nowThis.parseURLCallback('onEndLoad', url); 
				}
			};
			
			// We shouldn't really have to keep track of data sources if we use the composite datasource, but...
			this.dataSource.push(this.rdfService.GetDataSource(url));
			this.dataSource[this.dataSource.length-1].QueryInterface(Components.interfaces.nsIRDFXMLSink);
			this.dataSource[this.dataSource.length-1].addXMLSinkObserver(observer);		
			this.compositeDS.QueryInterface(Components.interfaces.nsIRDFCompositeDataSource);
			this.compositeDS.AddDataSource(this.dataSource[this.dataSource.length-1]);
	
			// Unregister Data Source from cache so expected loading behavior works each time dialog is opened
			// ... It'd be better not to have to do this but I can't find a way to know when a datasource is 
			// ... loaded independant of the observer, which doesn't seem to be called when the datasource is loaded from the cache (it is loaded too quickly)
			var dsaIter = this.compositeDS.GetDataSources();
			while( dsaIter.hasMoreElements() ) { // loop over datasources
				this.rdfService.UnregisterDataSource(dsaIter.getNext());
			}
	
			// wait till parseURLCallback is called with onEndLoad to do anything with this.
		},
		
		/** [private] Internal callback function; will track requests, send information to external callback function, 
		 * and continue with retrival when resource has loaded.
		 * @param observerMsg String specifying the type of message
		 * @param url String indicating which URL is related to the message
		 * @returns None
		 */ 
		parseURLCallback : function( observerMsg, url ) {
			if( observerMsg == "onInterrupt" || observerMsg == "onResume" ) { 
				return; // Do nothing, these messages seem routine and are clutter
			}
	
			this.userCallback.parseCallback(false, observerMsg + ": " + url);
	
			if(observerMsg == "onBeginLoad") {
	
			} else if(observerMsg == "onEndLoad") {
				this.endParseURL(url);
			}
		},
		/** [private] Called when a URL has been loaded and parsed, this function starts the recursive retrieval of 
		 * other RDF URL's defined with the the "owl:imports" tag, if it exists.
		 * @param url the URL that just finished loading and being parsed
		 * @returns None
		 */
		endParseURL : function(url) {
			this.urlsFollowed[url.toString()] = true; // Mark that it has loaded
	
			// Follow owl:imports
			var importedRDF = this.triplesInterface.getMatchTriplesList(null,"http://www.w3.org/2002/07/owl#imports",null);
			this.triplesInterface.clearTriples();  // Mark "cache" as old because we're not done loading RDF files
	
			// Here, you want to check that you've not yet tried to get these URL's and that they're not cached versions		
			var importedRDFs = new Array();
			for(var i in importedRDF) {
				importedRDF[i].object = rdfGetCachedURL(importedRDF[i].object);
				if(typeof this.urlsFollowed[importedRDF[i].object] == "undefined") {
					this.urlsFollowed[importedRDF[i].object.toString()] = false; // mark that we've started to follow this
					importedRDFs.push(importedRDF[i].object.toString());
				}
			}
			this.continueParse(importedRDFs);
	
			if( this.isAllLoaded() ) {
				this.endParse();
			}
		},
		/** [private] Called when all loading/parsing is done.
		 * @returns None
		 */
		endParse : function() {
	/*
			window.clearTimeout(this.loadingTimeoutTimer);
	*/
			this.userCallback.parseCallback(true, "Finished retriving all relevant RDF's");
		},
		/** Reports if all the URL's that we've claimed to have started have all finished loading.
		 * @returns True if all URL's have loaded, else False
		 */
		isAllLoaded : function() {
			for(var i in this.urlsFollowed) {
				if(this.urlsFollowed[i] == false) {
					return false;
				}
			}
			return true;
		},
	
		/** Not implemented */
		findRelated : function(objectName, predicate) {
			// This function attempts to find all RDF URL's that refer to the given object with the given predicate.
			// If the object is null, all URL's on either end of the predicates that match will be returned.
			// If the predicate is null, all URL's for subjects and objects that match name will be returned.
		},
		
		/* 
		 * Accessor functions passed to the RDFTriplesInterface layer, below.
		 */
			
		/** Debugging function, meant to be overridden with specific test cases after the parser has loaded.
		 * Currently, it pops up an alert for each triple in the datasources, displaying the triples contents.
		 */ 
		test : function() {
	/*
			var dsaIter = this.compositeDS.GetDataSources();
			while( dsaIter.hasMoreElements() ) { // loop over datasources
				ds = dsaIter.getNext();
				var resIter = ds.GetAllResources();
				while( resIter.hasMoreElements() ) {
					res = resIter.getNext().QueryInterface( Components.interfaces.nsIRDFResource );
					alert(res.Value);
				}
			}
	*/
			this.resetIteratorFn();
			do {
				this.nextFn();
				alert(this.getSubjectFn() + "\n" + this.getPredicateFn() + "\n" + this.getObjectFn());
			} while( this.nextResource != null );
		},
	
		/** Gets the next available resource, assumes resetIterator has been called.
		 * @returns none
		 */
		getNextResource : function() {
			// Gets the next available resource, updating the nextDataSource if necessary
			// Assumes that the dataSourceIter, nextDataSource, and resourceIter have been 
			// ... initialized by a call to resetIterator
			// sets nextResource to null on failure
		
			if( this.resourceIter.hasMoreElements() ) {
				this.nextResource = this.resourceIter.getNext().QueryInterface( Components.interfaces.nsIRDFResource );
				return;
			} else if( this.dataSourceIter.hasMoreElements() ) { 
				this.nextDataSource = this.dataSourceIter.getNext().QueryInterface(Components.interfaces.nsIRDFDataSource);
				this.resourceIter = this.nextDataSource.GetAllResources();
				this.getNextResource(); // recurse
				return;
			}
			this.nextResource = null;
		},
	
		/** Iterates to the next triple.
		 * @returns None
		 */
		nextFn : function() {
			// Just iterates through all targets, arcs, and subjects (resources), in that recursive order, for the hierarchy.
		
			if( this.dataSourceIter == null ) {
				this.resetIterator();
			}
	
			// Try to get the next arc and target for the current nextResource
			if( this.nextResource != null && this.targetIter.hasMoreElements() ) {
				this.nextTarget = this.targetIter.getNext();
				try {
					this.nextTarget.QueryInterface( Components.interfaces.nsIRDFResource);
				} catch( e ) {
					this.nextTarget.QueryInterface( Components.interfaces.nsIRDFLiteral);				
				}
			} else if( this.nextResource != null && this.arcIter.hasMoreElements() ) {
				this.nextArc = this.arcIter.getNext().QueryInterface( Components.interfaces.nsIRDFResource);
				this.targetIter = this.nextDataSource.GetTargets(this.nextResource, this.nextArc, true);
				if(this.targetIter.hasMoreElements() ) {
					this.nextTarget = this.targetIter.getNext();
					try {
						this.nextTarget.QueryInterface( Components.interfaces.nsIRDFResource);
					} catch( e ) {
						this.nextTarget.QueryInterface( Components.interfaces.nsIRDFLiteral);				
					}
				}
			} else {
				this.getNextResource();
				if( this.nextResource != null ) { // we have a next resource
					this.arcIter = this.nextDataSource.ArcLabelsOut(this.nextResource);
					if( this.arcIter.hasMoreElements() ) {
						this.nextArc = this.arcIter.getNext().QueryInterface( Components.interfaces.nsIRDFResource);
						this.targetIter = this.nextDataSource.GetTargets(this.nextResource, this.nextArc, true);
						if(this.targetIter.hasMoreElements() ) {
							this.nextTarget = this.targetIter.getNext();
							try {
								this.nextTarget.QueryInterface( Components.interfaces.nsIRDFResource);
							} catch( e ) {
								this.nextTarget.QueryInterface( Components.interfaces.nsIRDFLiteral);				
							}
						}
					}
				} else {
					// No more resources
				}
			}
		},
	
		/** Reports the end of iteration.
		 * Note it doesn't let you know if there <em>is</em> a next resource, but it does let you know if there is a 
		 * possibility of a next resource (if there are still empty dataSources, this will return true).
		 * @returns True if there is a possibility of a next resource, false otherwise.
		 */
		isEndFn : function() {
			return( !(this.dataSourceIter.hasMoreElements() || this.resourceIter.hasMoreElements()) );
		},
	
		/** Starts/Re-starts the iterator
		 * @returns None
		 */
		resetIteratorFn : function() {
			this.compositeDS.QueryInterface(Components.interfaces.nsIRDFDataSource);
			this.dataSourceIter = this.compositeDS.GetDataSources();
			if( this.dataSourceIter.hasMoreElements() ) {
				this.nextDataSource = this.dataSourceIter.getNext().QueryInterface(Components.interfaces.nsIRDFDataSource);
				this.resourceIter = this.nextDataSource.GetAllResources();
			}
			this.nextResource = null;
			
			// not really needed, but might catch bugs
			this.nextArc = null;
			this.nextTarget = null;
			this.arcIter = null;
			this.targetIter = null;
		},	
		/** @returns The current subject in the iteration */
		getSubjectFn : function() {
			if(this.nextResource != null) {
				return rdfGetOriginalResource(this.nextResource.Value);
			} else {
				return null;
			}
		},
		/** @returns The current predicate in the iteration */
		getPredicateFn : function() {
			if(this.nextResource != null) {
				return rdfGetOriginalResource(this.nextArc.Value);
			} else {
				return null;
			}
			
		},
		/** @returns The current object in the iteration */	
		getObjectFn : function() {
			if(this.nextResource != null) {
				return rdfGetOriginalResource(this.nextTarget.Value);
			} else {
				return null;
			}		
		}
	}
AccessibilityExtension.LIB.extend(AccessibilityExtension.LIB,
{
	newRDFParse : function(followPredicate, callback) {
		var n = new RDFParse(followPredicate, callback);
		return n;
	}		
});
}});